package com.fsgom.common.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.springframework.util.CollectionUtils;
import org.xml.sax.SAXException;

public class XmlHandler {
	private Node element = null;
	private static final Pattern PATTERN = Pattern.compile("\\$\\{[^\\}]*\\}");

	/**
	 * 생성자
	 * 
	 * @param inputStream {@link InputStream}
	 */
	public XmlHandler(InputStream inputStream) {
		this(inputStream, "UTF-8");
	}

	/**
	 * 생성자
	 * 
	 * @param inputStream {@link InputStream}
	 * @param encoding 인코딩
	 */
	public XmlHandler(InputStream inputStream, String encoding) {
		try {
			this.element = parseResource(inputStream, encoding);
		} catch (XmlHandlerException e) {
			throw new IllegalArgumentException("Invalid InputStream", e);
		}
	}

	/**
	 * 생성자
	 * 
	 * @param element {@link Node}
	 */
	public XmlHandler(Node element) {
		this.element = element;
	}

	private Element parseResource(InputStream inputStream, String encoding) throws XmlHandlerException {
		SAXReader saxReader = new SAXReader();

		if (encoding != null) {
			saxReader.setEncoding(encoding);
		}

		try {
			saxReader.getXMLReader().setFeature("http://apache.org/xml/features/allow-java-encodings", true);
			Element element = saxReader.read(inputStream).getRootElement();
			inputStream.close();

			return element;
		} catch (IOException e) {
			throw new XmlHandlerException(e);
		} catch (SAXException e) {
			throw new XmlHandlerException(e);
		} catch (DocumentException e) {
			throw new XmlHandlerException(e);
		}
	}

	/**
	 * key에 해당하는 값을 리턴한다
	 * 
	 * @param key 키
	 * @return 키가 존재하지 않으면 null을 리턴할 수 있다
	 */
	public String get(String key) {
		Node selected = element.selectSingleNode(key);

		if (selected == null) {
			return null;
		}

		String result = selected.getText();
		Matcher matcher = PATTERN.matcher(result);
		StringBuffer sb = new StringBuffer();

		while (matcher.find()) {
			String group = matcher.group();

			if (group == null) {
				break;
			}

			matcher.appendReplacement(sb, get(group.substring(2, group.length() - 1)));
		}

		matcher.appendTail(sb);

		return sb.toString();
	}

	/**
	 * key에 해당하는 값을 리턴하고, 키가 존재하지 않으면 defaultValue 값을 리턴한다
	 * 
	 * @param key 키
	 * @param defaultValue 기본값
	 * @return
	 */
	public String get(String key, String defaultValue) {
		String result = get(key);
		return result == null ? defaultValue : result;
	}

	/**
	 * key가 있는지의 여부를 리턴한다
	 * 
	 * @param key 키
	 * @return
	 */
	public boolean containsKey(String key) {
		return element.selectSingleNode(key) != null;
	}

	/**
	 * key에 해당하는 값 목록을 리턴한다
	 * 
	 * @param key 키
	 * @return key가 존재하지 않으면, emptyList를 리턴한다
	 */
	@SuppressWarnings("unchecked")
	public List<String> getValues(String key) {
		List<Node> nodes = element.selectNodes(key);

		if (nodes == null) {
			return Collections.emptyList();
		}

		List<String> values = new ArrayList<String>(nodes.size());

		for (Node element : nodes) {
			values.add(element.getText());
		}

		return values;
	}

	public XmlHandler peek(String path) {
		Node subConfigElement = element.selectSingleNode(path);

		if (subConfigElement == null) {
			return null;
		}

		return new XmlHandler(subConfigElement);
	}

	public XmlHandler select(String path) {
		XmlHandler handler = peek(path);

		if (handler == null) {
			return null;
		}

		return handler;
	}

	public List<XmlHandler> selectAll(String path) {
		List<XmlHandler> subConfigs = peekAll(path);

		if (CollectionUtils.isEmpty(subConfigs)) {
			return Collections.emptyList();
		}

		return subConfigs;
	}

	@SuppressWarnings("unchecked")
	public List<XmlHandler> peekAll(String path) {
		if (element == null) {
			return null;
		}

		List<Node> nodeList = element.selectNodes(path);

		if (nodeList == null) {
			return Collections.emptyList();
		}

		List<XmlHandler> subConfigs = new ArrayList<XmlHandler>(nodeList.size());

		for (Node element : nodeList) {
			subConfigs.add(new XmlHandler(element));
		}

		return subConfigs;
	}

	/**
	 * 루트 엘리먼트의 이름을 리턴한다
	 * 
	 * @return
	 */
	public String getRootName() {
		return element.getName();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		return element.asXML();
	}
}
